package main

import (
	"bufio"
	"fmt"
	dyvmsapiclient "github.com/alibabacloud-go/dyvmsapi-20170525/client"
	rpc "github.com/alibabacloud-go/tea-rpc/client"
	"github.com/bamzi/jobrunner"
	"github.com/gin-gonic/gin"
	_ "net/http/pprof"
	"os"
	"runtime"
	"strings"
	"time"
	"github.com/fatih/color"
	//console "github.com/alibabacloud-go/tea-console/client"
	"github.com/alibabacloud-go/tea/tea"
	"github.com/darjun/go-daily-lib/jobrunner/sqldao"
)

/**
开发中时常会遇到这样的需求，执行一个操作之后，需要给用户一定形式的通知。例如，用户下单之后通过邮件发送电子发票，网上购票支付后通过短信发送车次信息。
但是这类需求并不需要非常及时，如果放在请求流程中处理，会影响请求的响应时间。这类任务我们一般使用异步的方式来执行。
jobrunner就是其中一个用来执行异步任务的 Go 语言库。得益于强大的cron库，再搭配jobrunner的任务状态监控，jobrunner非常易于使用。
*/
type GreetingJob struct {
	Name string
}

//golang的OOP编程语法比较特殊：
//1. 先用type XXX struct {} 声明一个结构体，但是里面只放字段，不放方法
//2. 直接用func(x XXX) method1() {}、直接用func(x XXX) method2() {}、直接用func(x XXX) method3() {}直接定义方法体，
//这样就算是XXX这个结构体定义了方法了，然后就可以x.method1调用了，很怪异的存在，和c++、java、python等完全不一样，真不太适应
func (g GreetingJob) Run() {
	fmt.Println("hello, ", g.Name)
}

func jobSchedule() {
	jobrunner.Start()
	jobrunner.Schedule("@every 5s", GreetingJob{Name: "Andy"})
	time.Sleep(2 * time.Second)
}

type User struct {
	Name  string `form:"name"`
	Email string `form:"email"`
}

type Config struct {
	WeekStartDay time.Weekday
	TimeLocation *time.Location
}

//用于接受http请求中的电话号码和语音文件ID
type VoiceCallEntity struct {
	Telephone string `form:"telephone"`
	VoiceId string	`form:"voiceId"`
}

//路由例子，http.get请求，从request中读取name和email这2个字段
//启动后访问：http://localhost:8888/login?name=中国好&email=935653229@qq.com
func login(c *gin.Context) {
	var u User
	if c.ShouldBind(&u) == nil {
		//根据User结构体的字段名称，自动从http请求中匹配数据，所以上面的http请求中的name和email字段，会被匹配到User对象中，
		//因为我们传递进去的是u的指针，所以就可以对它赋值了，我估计是通过golang的reflect包做到的！
		c.String(200, "login success，登录用户的名称是：" + u.Name)
	} else {
		c.String(404, "login failed")
	}
}

//http.get 客户端发起类似下面的调用，即可拨打电话
//http://localhost:8888/voicecall?telephone=18896723719&voiceId=86747b6c-0f5f-4a4c-b5c8-9df904677555.wav
func MakeVoiceCall(c *gin.Context) {
	var entity VoiceCallEntity
	if c.ShouldBind(&entity) == nil {
		fmt.Println("MakeVoiceCall收到请求参数：", entity.Telephone, entity.VoiceId)
		makeSingleCall(entity.Telephone, entity.VoiceId)
		c.String(200, "电话已呼出，请耐心等待...")
	} else {
		c.String(404, "参数错误，格式应该是telephone=xxxx&voiceId=xxxx")
	}
}

//启动http server，在8888端口监听
func ginHttpServer() {
	gin.SetMode(gin.ReleaseMode)
	router := gin.Default()
	router.GET("/login", login) //这个就是路由绑定，所有框架都有，包括vue
	router.GET("/voicecall", MakeVoiceCall)
	router.Run(":8888")
}

//golang的函数返回值，竟然是放在方法签名后面的，用括号括起来，多个返回值用逗号隔开，也是醉了
func makeSingleCall(telephone string, videoId string) (result bool) {
	fmt.Println("正在发起呼叫，请稍候...")
	client, err := createVideoCallClient(tea.String("qlo4BoLGXaAXU7FA"), tea.String("wHT8XCsYTnaj7utm5L0t1f1owmwSTy"), tea.String("cn-hangzhou"))
	if err != nil {
		return false
	}
	request := &dyvmsapiclient.SingleCallByVoiceRequest{
		CalledShowNumber: tea.String(""),
		CalledNumber: tea.String(telephone),
		VoiceCode: tea.String(videoId),
	}

	response, err := client.SingleCallByVoice(request)
	if err != nil {
		fmt.Println("发起呼叫失败", err)
		return false
	}
	fmt.Println("呼叫发起完成，运营商返回值：\n", response.GoString())
	return true
}

//查询某次呼叫的通话详情
func querySingleCallStatus(callId string) bool {
	fmt.Println("正在查询结果，请稍候：")
	client, err := createVideoCallClient(tea.String("qlo4BoLGXaAXU7FA"), tea.String("wHT8XCsYTnaj7utm5L0t1f1owmwSTy"), tea.String("cn-hangzhou"))
	if err != nil {
		return false
	}
	request2 := &dyvmsapiclient.QueryCallDetailByCallIdRequest{
		CallId: tea.String(callId),
		ProdId: tea.Int64(11000000300006),
		QueryDate: tea.Int64(time.Now().Unix()),
	}
	response, err := client.QueryCallDetailByCallId(request2)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return false
	}
	fmt.Println(response.Data)
	return true
}

/*
创建一个发起语音呼叫的客户端对象
 */
func createVideoCallClient(accessKeyId *string, accessKeySecret *string, regionId *string) (*dyvmsapiclient.Client, error) {
	config := &rpc.Config{} //&语法直接创建一个指针，所以现在config实际上是 var config * rpc.Config = &rpc.Config{}
	config.AccessKeyId = accessKeyId
	config.AccessKeySecret = accessKeySecret
	config.RegionId = regionId
	config.ConnectTimeout = tea.Int(2000)

	_client, _err := dyvmsapiclient.NewClient(config)
	return _client, _err
}

func createShell() {
	//fmt.Fprintln(os.Stderr,"Welcome to shell env, please enter commands：ls、help、dir")
	c := color.New(color.FgCyan).Add(color.Underline)
	c.Println("交互式电话拨打程序（运营商为阿里云）, 请输入支持的命令：call、query、exit、help")

	green := color.New(color.FgGreen, color.Bold)
	red := color.New(color.FgRed)

	var reader *bufio.Reader = bufio.NewReader(os.Stdin)
	for { //golang里面的无限循环就是这样的
		fmt.Print("$")
		command, err := reader.ReadString('\n')
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
		command = strings.TrimSuffix(command, "\n")
		command = strings.TrimSpace(command)
		//if strings.Compare(command, "ls") == 0 {
		//	fmt.Println("输入了ls命令")
		//}
		if command == "help" || command == "h" {
			green.Println("\n*********************************************************************************\n" +
				"支持命令行及Http API调用两种方式拨打电话\n1. 命令行方式：call telephone voiceId #用来调用阿里云服务打电话，voiceId需要预先知道\n" +
				"2. Http API方式，在浏览器中输入http://localhost:8888/voicecall?telephone=xxxxx&voiceId=xxxxxx.wav\n" +
				"3. 任意编程语言发起Http Get请求，格式：http://localhost:8888/voicecall?telephone=xxxxx&voiceId=xxxxxx.wav\n" +
				"4. query callId命令用于查询某通电话的通话详情，callId是由call命令返回的，请注意保存\n" +
				"5. exit 退出当前程序\n\n" +
				"示例：\n\n" +
				"拨打电话输入：call 18896723719 86747b6c-0f5f-4a4c-b5c8-9df904677555.wav\n" +
				"查询通话详情输入：query 120487527860^107282832860\n\n" +
				"*********************************************************************************")
		} else if strings.HasPrefix(command, "call") {
			var fields []string = strings.Fields(command)
			if len(fields) != 3 {
				red.Fprintln(os.Stderr, "拨打电话格式错误，正确的格式是：call 139140xxxxx 86747b6c-0f5f-4a4c-b5c8-9df904677555.wav，" +
					"第一个参数是电话号码，第二个参数是语音文件的ID")
				continue
			}
			telephone := strings.TrimSpace(fields[1])
			voiceId := strings.TrimSpace(fields[2])
			if !strings.HasSuffix(voiceId, ".wav") {
				red.Fprintln(os.Stderr, "语音文件必须以.wav结尾")
				continue
			}
			makeSingleCall(telephone, voiceId)
		} else if strings.HasPrefix(command, "query") {
			var fields []string = strings.Fields(command)
			if len(fields) != 2 {
				red.Fprintln(os.Stderr,"查询通话记录命令错误，正确的格式是：query callId，其中callId是一串字符串，由call命令返回，请注意保存")
				continue
			}
			var callId string = strings.TrimSpace(fields[1])
			querySingleCallStatus(callId)
		} else if command == "gc" {
			runtime.GC()
			c.Println("GC完成")
		} else if command == "exit" || command == "q" {
			c.Println("Bye, welcome back!")
			os.Exit(1)
		} else {
			green.Fprintln(os.Stderr,"支持的命令：help、call、query、exit、gc五个命令，请任意输入这5个命令后回车即可")
		}
	}
}

func main() {
	sqldao.Init()
	//fmt.Println("start to call...")
	//result := makeSingleCall("18896723719", "86747b6c-0f5f-4a4c-b5c8-9df904677555.wav")
	//fmt.Println("call finished...", result)
	go createShell()
	ginHttpServer()
}
